package com.strato.annotation.base.v1_0_0.scanner;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;

import com.strato.annotation.base.v1_0_0.activator.Activator;
import com.strato.annotation.base.v1_0_0.handler.AnnotationHandler;
import com.strato.annotation.base.v1_0_0.registry.AnnotationRegistry;
import com.strato.annotation.base.v1_0_0.registry.BootStateful;
import com.strato.annotation.base.v1_0_0.registry.BundleDescriptor;
import com.strato.annotation.base.v1_0_0.registry.ClassDescriptor;
import com.strato.base.osgi.v1_0_0.BundleUtil;
import com.strato.logging.v1_0_0.api.Log;
import com.strato.logging.v1_0_0.api.LogFactory;
import com.strato.osgi.scanner.v1_0_0.AbstractFrameworkBundleListener;

public class AnnotationScanner extends AbstractFrameworkBundleListener implements BootStateful{

	private Log log=LogFactory.getLog(getClass());
	
	@Override
	public void onResolve(Bundle bundle) {
		lock();
		try{
			if(bundle.getBundleId()==0){//skip system bundle
				return;
			}
			ZipFile zipFile=null;
			try {
				log.info("Resolving "+bundle.getSymbolicName());
				AnnotationRegistry registry=AnnotationRegistry.getInstance();
				BundleDescriptor bundleDescriptor=new BundleDescriptor();
				bundleDescriptor.setName(bundle.getSymbolicName());
				bundleDescriptor.setVersion(bundle.getVersion().toString());
				registry.getBundleDescriptors().add(bundleDescriptor);
				URL url=new URL(bundle.getLocation());
				File file = new File(url.getFile());
				zipFile=new ZipFile(file);
				Enumeration<? extends ZipEntry> entries = zipFile.entries();
				BundleContext context = Activator.getContext();
				List<AnnotationHandler> handlers = BundleUtil.getServices(context, AnnotationHandler.class, null);
				while(entries.hasMoreElements()){
					ZipEntry entry=entries.nextElement();
					String fileName=entry.getName();
					if(fileName.endsWith(".class")){
						String className=extractClassName(fileName);
						if(!isSupportedClass(className,bundle)){
							continue;
						}
						if(log.isDebugEnabled()){
							log.debug("Scanning class "+className+" for annotations");
						}
						Class<?> clazz = loadClass(bundle,className);
						if(clazz!=null){
							discoverAnnotations(bundle,clazz,bundleDescriptor,handlers);
						}
					}
				}
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (ZipException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}finally{
				if(zipFile!=null){
					try {
						zipFile.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}finally{
			unlock();
		}
	}

	private boolean isSupportedClass(String className, Bundle bundle) {
		if(className.startsWith("com.strato.")){
			return true;
		}
		int waitTimes=0;
		while(bundle.getBundleContext()==null&&waitTimes<500){//wait until bundle to be resolved
			try {
				waitTimes++;
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
				break;
			}
		}
		if(bundle.getState()==Bundle.UNINSTALLED){
			return false;
		}
		if(bundle.getBundleContext()==null){
			return false;
		}
		String packages=bundle.getBundleContext().getProperty("strato.annotation.packages");
		if(packages!=null&&!"".equals(packages.trim())){
			String[] pkgs=packages.split(",");
			for(String pkg:pkgs){
				pkg=pkg.trim();
				if(className.startsWith(pkg)){
					return true;
				}
			}
		}
		return false;
	}

	private Class<?> loadClass(Bundle bundle, String className) {
		try {
			return bundle.loadClass(className);
		} catch (Throwable e) {
			return null;
		}
	}

	private void discoverAnnotations(Bundle bundle, Class<?> clazz, BundleDescriptor bundleDescriptor, List<AnnotationHandler> handlers) {
		ClassDescriptor classDescriptor=new ClassDescriptor();
		classDescriptor.setBundleDescriptor(bundleDescriptor);
		classDescriptor.discover(clazz);
		bundleDescriptor.getClassDescriptors().add(classDescriptor);
		for(AnnotationHandler handler:handlers){
			handler.onDiscoverClass(bundle,clazz,classDescriptor);
		}
	}

	private String extractClassName(String fileName) {
		return fileName.substring(0, fileName.length()-".class".length()).replaceAll("/", ".");
	}

	@Override
	public void onStart(Bundle bundle){
		lock();
		try{
			AnnotationRegistry registry=AnnotationRegistry.getInstance();
			BundleDescriptor condition=new BundleDescriptor();
			condition.setName(bundle.getSymbolicName());
			condition.setVersion(bundle.getVersion().toString());
			BundleDescriptor bundleDescriptor=registry.find(condition);
			if(bundleDescriptor==null){
				return;
			}
			List<AnnotationHandler> handlers = BundleUtil.getServices(bundle.getBundleContext(), AnnotationHandler.class, null);
			AnnotationRegistry.getInstance().applyHandlers(bundleDescriptor,handlers);
		}finally{
			unlock();
		}
	}

	@Override
	public void onUnresolved(Bundle bundle) {
		lock();
		try{
			if(bundle.getBundleId()==0){//skip system bundle
				return;
			}
			List<AnnotationHandler> handlers = BundleUtil.getServices(Activator.getContext(), AnnotationHandler.class, null);
			AnnotationRegistry registry=AnnotationRegistry.getInstance();
			BundleDescriptor condition=new BundleDescriptor();
			condition.setName(bundle.getSymbolicName());
			condition.setVersion(bundle.getVersion().toString());
			BundleDescriptor bundleDescriptor=registry.find(condition);
			if(bundleDescriptor!=null){
				registry.getBundleDescriptors().remove(bundleDescriptor);
			}
			for(AnnotationHandler handler:handlers){
				List<ClassDescriptor> cds = bundleDescriptor.getClassDescriptors();
				if(cds!=null){
					for(ClassDescriptor classDescriptor:cds){
						handler.handleUnresolveClass(bundle,classDescriptor);
					}
				}
				handler.handleUnresolve(bundle,bundleDescriptor);
			}
		}finally{
			unlock();
		}
	}

	private boolean initialScanFinished=false;
	
	private boolean locked=false;
	private void lock(){
		locked=true;
	}
	private void unlock(){
		locked=false;
	}
	public AnnotationScanner initialScan(BundleContext bundleContext) {
		try{
			Bundle[] bundles = bundleContext.getBundles();
			for(Bundle bundle:bundles){
				if(bundle.getState()==Bundle.RESOLVED){
					onResolve(bundle);
				}else if(bundle.getState()==Bundle.ACTIVE){
					onResolve(bundle);
					onStart(bundle);
				}
			}
		}finally{
			initialScanFinished=true;
		}
		return null;
	}

	@Override
	public boolean isReady() {
		return initialScanFinished&&!locked;
	}

}
